package com.example.file_operate_demo.controller;

import org.apache.tomcat.util.http.fileupload.IOUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.util.ObjectUtils;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.net.FileNameMap;
import java.net.URLConnection;
import java.net.URLEncoder;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

/**
 * 文件操作请求处理控制
 */
@RestController
@RequestMapping("file")
public class FileController {

    private final static Logger log = LoggerFactory.getLogger(FileController.class);

    private final static String UPLOAD_DIR = "uploads/";

    /**
     * 文件上传
     *
     * @param files 上传文件集合
     * @return  上传结果
     */
    @PostMapping("/upload")
    @ResponseBody
    public ResponseEntity<String> fileUpload(@RequestParam("files") List<MultipartFile> files) {
        try {
            // 获取资源文件存放路径
            String path = Objects.requireNonNull(this.getClass().getClassLoader().getResource("")).getPath();

            // 创建文件上传目录，如果目录不存在
            createDirectoryIfNotExist(path + UPLOAD_DIR);

            for (MultipartFile file : files) {
                // 获取文件上传目录的相对路径
                String uploadFilePath = path + UPLOAD_DIR + file.getOriginalFilename();;

                // 创建目标文件
                File dest = new File(uploadFilePath);

                // 向指定路径写入文件
                file.transferTo(dest);
            }

            return new ResponseEntity<>(null, HttpStatus.OK);
        } catch (Exception e) {
            e.printStackTrace();
            log.info(String.format("文件上传失败，原因：%s", e));
            return new ResponseEntity<>("文件上传失败", HttpStatus.INTERNAL_SERVER_ERROR);
        }
    }

    /**
     * 文件下载
     *
     * @param filename 下载文件名
     */
    @GetMapping("/download")
    public void download(@RequestParam("filename") String filename, HttpServletResponse response) throws IOException {
        // 创建临时ZIP文件
        String tempZipFileName = createTempZipFileName();
        File zipFile = new File(tempZipFileName);
        List<String> filePathList = getFilePathList(filename);

        try (ZipOutputStream zipStream = new ZipOutputStream(new FileOutputStream(zipFile))) {
            if (filePathList.size() == 1) {
                // 下载单个文件
                downloadSingleFile(filePathList.get(0), response);
            } else {
                // 打包下载多个文件
                downloadMultipleFiles(filePathList, response, zipStream, zipFile, tempZipFileName);
            }
        } finally {
            // 删除临时ZIP文件
            if (zipFile.exists()) {
                zipFile.delete();
            }
        }
    }

    /**
     * 删除文件
     *
     * @param data  请求参数, 文件名
     * @return  删除结果
     */
    @PostMapping("/delete")
    @ResponseBody
    public Map<String, Boolean> deleteFile(@RequestBody Map<String, String> data) {
        String fileName = data.get("fileName");

        Map<String, Boolean> response = new HashMap<>();
        List<String> filePathList = getFilePathList(fileName);

        // 获取资源文件存放路径
        String path = Objects.requireNonNull(this.getClass().getClassLoader().getResource("")).getPath();

        for (String file : filePathList) {
            // 拼接完整路径
            File delFile = new File(path + UPLOAD_DIR + file);

            // 删除文件
            if (delFile.exists()) {
                boolean deleted = delFile.delete();
                response.put("success", deleted);
            } else {
                response.put("success", false);
            }
        }

        return response;
    }

    /**
     * 文件预览
     *
     * @param fileName  文件名
     */
    @GetMapping("/preview")
    public void previewFile(@RequestParam String fileName, HttpServletResponse response) {
        // 获取资源文件存放路径
        String path = Objects.requireNonNull(this.getClass().getClassLoader().getResource("")).getPath();

        // 拼接完整路径
        File file = new File(path + UPLOAD_DIR + fileName);

        if (file.exists()) {
            try (InputStream inputStream = new FileInputStream(file)) {
                response.setContentType(getContentType(fileName));
                response.setHeader("Content-Disposition", "inline; filename=" + file.getName());

                IOUtils.copy(inputStream, response.getOutputStream());
                response.flushBuffer();
            } catch (IOException e) {
                e.printStackTrace();
            }
        } else {
            // 文件不存在
            response.setStatus(HttpServletResponse.SC_NOT_FOUND);
        }
    }

    /**
     * 获取内容类型
     *
     * @param fileName  文件名
     * @return  内容类型
     */
    private String getContentType(String fileName) {
        FileNameMap fileNameMap = URLConnection.getFileNameMap();
        return fileNameMap.getContentTypeFor(fileName);
    }

    /**
     * 查询文件列表
     *
     * @return 文件名称列表
     */
    @GetMapping("/list")
    @ResponseBody
    public ResponseEntity<List<String>> getFileList() {
        // 获取资源文件存放路径
        String path = Objects.requireNonNull(this.getClass().getClassLoader().getResource("")).getPath();

        // 拼接完整路径
        File pathFile = new File(path + UPLOAD_DIR);
        File[] files = pathFile.listFiles();

        List<String> fileList = new ArrayList<>();
        if (ObjectUtils.isEmpty(files)) {
            return new ResponseEntity<>(fileList, HttpStatus.OK);
        }

        for (File file : files) {
            if (file.isFile()) {
                fileList.add(file.getName());
            }
        }

        return new ResponseEntity<>(fileList, HttpStatus.OK);
    }

    /**
     * 创建目录，如果目录不存在
     *
     * @param directoryPath 目录路径
     */
    private void createDirectoryIfNotExist(String directoryPath) {
        // 获取项目的相对路径, 并拼接目录的相对路径
        File dir = new File(directoryPath);
        if (!dir.exists()) {
            dir.mkdirs();
        }
    }

    /**
     * 根据文件路径进行拆分
     *
     * @param filePath  文件路径
     * @return  拆分出来的文件路径列表
     */
    private static List<String> getFilePathList(String filePath) {
        List<String> filePathList = new ArrayList<>();

        if (filePath.contains(",")) {
            // 校验处理文件路径
            filePathList = Arrays.asList(filePath.split(","));
        } else {
            filePathList.add(filePath);
        }

        return filePathList;
    }

    /**
     * 生成临时ZIP文件名称
     *
     * @return 临时ZIP文件名称
     */
    private String createTempZipFileName() {
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyyMMddHHmmss");
        LocalDateTime now = LocalDateTime.now();
        return "download_" + now.format(formatter) + ".zip";
    }

    /**
     * 下载单个文件
     *
     * @param filePath 文件路径
     * @param response HttpServletResponse对象
     * @throws IOException IO异常
     */
    private void downloadSingleFile(String filePath, HttpServletResponse response) throws IOException {
        // 获取资源文件存放路径
        String path = Objects.requireNonNull(this.getClass().getClassLoader().getResource("")).getPath();

        // 获取完整路径和文件
        File file = new File(path + UPLOAD_DIR + filePath);

        // 文件不存在
        if (!file.exists()) {
            response.setStatus(HttpServletResponse.SC_NOT_FOUND);
            return;
        }

        String fileName = file.getName();

        response.setContentType("application/x-msdownload");
        response.setHeader("Content-Disposition", "attachment; filename=" + URLEncoder.encode(fileName, "UTF-8"));
        response.setHeader("Content-Length", String.valueOf(file.length()));

        try (InputStream in = new FileInputStream(file);
             OutputStream out = response.getOutputStream()) {
            IOUtils.copy(in, out);
            out.flush();
        }
    }

    /**
     * 打包下载多个文件
     *
     * @param filePaths 要下载的文件路径列表
     * @param response  HttpServletResponse对象
     * @param zipStream ZipOutputStream对象
     * @throws IOException IO异常
     */
    private void downloadMultipleFiles(List<String> filePaths, HttpServletResponse response, ZipOutputStream zipStream, File zipFile, String tempZipFileName) throws IOException {

        response.setContentType("application/x-msdownload");
        response.setHeader("Content-Disposition", "attachment; filename=" + URLEncoder.encode(tempZipFileName, "UTF-8"));

        // 获取资源文件存放路径
        String path = Objects.requireNonNull(this.getClass().getClassLoader().getResource("")).getPath();

        for (String filePath : filePaths) {
            // 获取完整路径和文件
            File file = new File(path + UPLOAD_DIR + filePath);

            // 文件不存在, 跳过
            if (!file.exists()) continue;

            // 将文件添加到ZIP中
            addFileToZip(file, zipStream);
        }

        response.setHeader("Content-Length", String.valueOf(zipFile.length()));

        try (InputStream in = new FileInputStream(zipFile);
             OutputStream out = response.getOutputStream()) {
            IOUtils.copy(in, out);
            out.flush();
        }

    }

    /**
     * 将文件添加到ZIP中
     *
     * @param file      要添加的文件
     * @param zipStream ZipOutputStream对象
     * @throws IOException IO异常
     */
    private void addFileToZip(File file, ZipOutputStream zipStream) throws IOException {
        byte[] buffer = new byte[1024 * 1024 * 100];
        try (FileInputStream fileInputStream = new FileInputStream(file);
             BufferedInputStream bufferedInputStream = new BufferedInputStream(fileInputStream);
             ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream()) {

            int bytesRead;
            while ((bytesRead = bufferedInputStream.read(buffer)) != -1) {
                byteArrayOutputStream.write(buffer, 0, bytesRead);
            }

            ZipEntry zipEntry = new ZipEntry(file.getName());
            zipStream.putNextEntry(zipEntry);
            zipStream.write(byteArrayOutputStream.toByteArray());
            zipStream.closeEntry();
        }
    }

}
